/*
	J-Bird net/sourceforge/jbird/util/Utils.java

        Copyright (C) 2001-2007 Dick Repasky
	drepasky@users.sourceforge.net

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License 
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	General Public License for more details.

	You should have received a copy of the GNU General Public 
	License along with this program; if not, write to the Free Software 
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/
package net.sourceforge.jbird.util;

import java.util.ArrayList;
import java.util.Calendar;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;

/**
  * Miscellaneous static utilities.
  * @author Dick Repasky
  * @since J-Bird 0.1.2
  *
*/

public final class Utils {

	/**
	  * Extract values from an ArrayList of integers and return them
	  * as an int array.  Null values are returned as Integer.MIN_VALUE.
	  * @since J-Bird 0.5.0
	  *
	*/

  public static final int[] arrayListToInt(ArrayList al) {
	  final int ceil = al.size();
	  int[] answer = new int[ceil];
	  for (int idx = 0; idx < ceil; idx ++) {
		  Integer anint = (Integer)al.get(idx);
		  answer[idx] = anint != null ? anint.intValue()
			  			: Integer.MIN_VALUE;
	  }
	  return answer;
  }

	/**
	  * Capitalize each word in the text.  Words are dilimitted by
	  * runs of white space. Runs are collapsed to single spaces.
	  * @since J-Bird 0.4.1
	  *
	*/

  public static final String capitalizeAllWords(String text) {
	if (text == null || text.length() < 1) {
		return text;
	}
	String[] words = split(text);
	int len = words.length;
	StringBuffer answer = new StringBuffer(text.length());
	for (int idx = 0; idx < len; idx ++) {
		if (idx > 0) {
			answer.append(" ");
		}
		answer.append(capitalizeWord(words[idx]));
	}
	return answer.toString();
  }

	/**
	  * Capitalize word provided, as is. No effort is made to
	  * trim white space from the word. The first position is
	  * assumed to be the first letter of the word.
	  * @since J-Bird 0.4.1
	  *
	*/

  public static final String capitalizeWord(String word) {
	if (word == null || word.length() < 1) {
		return word;
	}
	int wordlen = word.length();
	StringBuffer sb = new StringBuffer(wordlen);
	int at = 0;
	char charat = word.charAt(at);
	while (! Character.isLetter(charat) && at < wordlen) {
		sb.append(charat);
		at ++;
		charat = word.charAt(at);
	}
	if (at < wordlen) {
		int bdy = at + 1;
		sb.append(word.substring(at, bdy).toUpperCase());
		if (bdy < wordlen) {
			sb.append(word.substring(bdy).toLowerCase());
		}
	}
	return sb.toString();
  }

	/**
	  * Calculate the point at which a window of the given size 
	  * should be placed to center it on the screen.
	  * @since J-Bird 0.4.0
	*/
  public static final Point center(Dimension size) {
	return center(size, 2);
  }

	/**
	  * Calculate the point at which a window of the given size 
	  * should be placed to center it on the screen horizontally
	  * but specify a denominator for placing it vertically. 
	  * 1 places it a the top, 2 centers it, 3 places it at a third
	  * of the distance from the top tha would be required to center it.
	  * @since J-Bird 0.4.0
	*/
  public static final Point center(Dimension size, int vertdenom) {
		Dimension screendim = Toolkit.getDefaultToolkit()
						.getScreenSize();
		int xoffset = (screendim.width - size.width) / 2;
		int yoffset = (screendim.height - size.height) / vertdenom;
		return new Point(xoffset, yoffset);
  }

	/**
	  * Take the last word of a string and place it at the
	  * beginning, followed by a comma
	  * @since J-Bird 0.3.0 (moved from JBirdDBTools)
	*/

   public static final String commaizeName(String str) {
	int len = str.length();
	int at = str.lastIndexOf(' ');
	if (at < 0) {
		return str;
	}
	return new StringBuffer((len + 1))
		.append(str.substring((at + 1)))
		.append(", ")
		.append(str.substring(0, at))
		.toString();
   }

   	/**
	  * Determine determine the type of linefeed that is in the
	  * string.  Replace linefeeds with the correct type if they
	  * do not match the system property "line.separator".
	  * Strings that do not contain '\n' are not processed.
	  * @since J-Bird 0.5.1
	  *
	*/

   public static final String correctLineFeeds(String str) {
   	if (str.indexOf('\n') < 0) return str;
	final String EOL = System.getProperty("line.separator");
   	final String doslf = "\r\n";
	if (EOL.equals(doslf) && str.indexOf(EOL) < 0) {
				// unix linefeeds on dos system
		str = str.replaceAll("\n", EOL);
	} else if ("\n".equals(EOL) && str.indexOf(doslf) > -1) {
				// dos linefeeds on unix system
		str = str.replaceAll("\r", "");
	}
	return str;
   }

	/**
	  * Remove comma from name, and rearrange.  For example,
	  * "Carmichael, Hoagy" becomes "Hoagy Carmichael". 
	  * @since J-Bird 0.4.1
	  *
	*/

   public static final String decommaName(String str) {
	int at = str.indexOf(',');
	if (at < 0) {
		return str;
	}
	int len = str.length();
	int beginat = at;
	char achar = str.charAt(beginat);
	while (achar == ' ' || achar == '\t' || achar == ',') {
		beginat ++;
		if (beginat < len) {
			achar = str.charAt(beginat);
		} else {
			return str.substring(0, at).trim();
		}
	}
	StringBuffer answer = new StringBuffer(len);
	answer.append(str.substring(beginat).trim());
	answer.append(' ');
	answer.append(str.substring(0, at).trim());
	return answer.toString();
   }

  /**
    * Truncate string to length given.
    * <p>
    * Moved form JbirdDB in 0.4.0
  */

   public static final String clipToMax(String value, int maxlen) {

      int valuelen = value.length();
      String answer = null;
	
      if (valuelen > maxlen) {
	answer = value.substring(0, (maxlen - 1));
      } else {
	answer = value;
      }
      return answer;
   }

	/** 
	  * Count words in string.  Words are runs of non-whitespace,
	  * separated by runs of whitespace.
	  * @since J-Bird 0.3.0
	  *
	*/

   public static final int countWords(String str) {
	int max = str.length();
	int answer = 0;
	boolean inword = false;
	for (int idx = 0; idx < max; idx ++) {
		boolean iswhite = Character.isWhitespace(str.charAt(idx));
		if (inword) {
			if (iswhite) {
				inword = false;
			}
		} else {
			if (!iswhite) {
				inword = true;
				answer ++;
			}
		}
	}
	return answer;
   }

	/**
	  * Return true if every element of a is equal to the corresponding
	  * element of b.
	  * Return false otherwise, including if lengths are not equal.
	  * Arguments may not be false.
	  * @since J-Bird 0.3.0
	  *
	*/

   public static final boolean equals(String[] a, String[] b) {
	int alength = a.length;
	int blength = b.length;
	if (alength != blength) {
		return false;
	}
	for (int idx = 0; idx < alength; idx ++) {
		if ( ! a[idx].equals(b[idx])) {
			return false;
		}
	}
	return true;
   }

	/**
	  * In the text provided, insert the escape character in front
	  * of all escapees.
	  * @since J-Bird 0.1.3
	  *
	*/

  public static final String escapeChar(String text, char escapee,
					char escapechar) {
	if (text == null) {
		return null;
	}
	int loc = text.lastIndexOf(escapee);
	if (loc < 0) {
		return text;
	}
	StringBuffer sb = new StringBuffer(text);
	while (loc > -1) {
		sb.insert(loc, escapechar);
		loc --;
		if (loc >= 0) {
			loc = text.lastIndexOf(escapee, loc);
		}
	}
	return sb.toString();
  }

	/** 
	  * Escape single quotes with backslashes.
	  * <p>
	  * Moved from JbirdDB 0.4.0
	*/

   public static final String escapeQuotes(String str) {
	return escapeChar(str, '\'', '\\');
   }


	/**
	  * Extract body of an html document, sans the body tags.
	  * Take from net.sourceforge.jbird.TripReporter in J-Bird 0.2.0
	  * @since J-Bird 0.2.0
	  *
	*/

   public static final String getHTMLBody(String html) {
	String finder = html.toLowerCase();
	int last = finder.lastIndexOf("</body");
	int first = finder.indexOf("<body");
	first = finder.indexOf('>', first) + 1;
	String answer;
	if (first > -1 && last > first) {
		answer = html.substring(first, last);
	} else {
		answer = stripHTMLTags(html);
	}
	return answer;
   }

	/**
          * Given a string (passage) that is a set of
	  * lines, indent each line using the prefix 
	  * provided.
	*/

  public static final String indent(String passage,
				String prefix) {
	return indent(passage, prefix, true);
  }

	/**
	  * Given a string (passage) that is a set of lines,
	  * indent each line using the prefix.  The parameter 
	  * indentfirstln determines whether the first line is indented.
	  *
	*/
  public static final String indent(String passage,
				String prefix, boolean indentfirstln) {
	int max = passage.length();
	StringBuffer buf = new StringBuffer(max + 20);
	boolean on_nl = indentfirstln;
	for (int idx = 0; idx < max; idx ++) {
		char achar = passage.charAt(idx);
		if (! on_nl) {
			if (achar == '\n') {
				on_nl = true;
			}
		} else {
			if (achar != '\n') {
				on_nl = false;
				buf.append(prefix);
			}
		}
		buf.append(achar);
	}
	return buf.toString();
  }

		/**
		  * Test whether all letters in text are lower case. 
		  * Non-letters are OK.  Returns false if all characters
		  * are non-letters.
		  * @since J-Bird 0.4.1
		  *
		*/

	public static boolean isAllLowerCase(String text) {
		int max = text.length();
		boolean answer = false;
		for (int idx = 0; idx < max; idx ++) {
			char charat = text.charAt(idx);
			if (Character.isLetter(charat)) { 
				if (Character.isUpperCase(charat)) {
					return false;
				}
				answer = true;	// found lower case
			}
		}
		return answer;
	}

		/**
		  * Test whether all letters in text are upper case. 
		  * Non-letters are OK. Returns false if all characters are
		  * non-letters.
		  * @since J-Bird 0.4.1
		  *
		*/

	public static boolean isAllUpperCase(String text) {
		int max = text.length();
		boolean answer = false;
		for (int idx = 0; idx < max; idx ++) {
			char charat = text.charAt(idx);
			if (Character.isLetter(charat)) { 
				if (Character.isLowerCase(charat)) {
					return false;
				}
				answer = true;	// found upper case
			}
		}
		return answer;
	}

		/**
		  * Return true if the first letter is capital and the second is
		  * not.  Otherwise return false.
		  * @since J-Bird 0.3.0
		*/

	public static final boolean isCapitalized(String string) {
		boolean answer = false;
		int at = 0;
		char charat = string.charAt(0);
		int strlen = string.length();
		while (! Character.isLetter(charat) && at < strlen) {
			at ++;
			charat = string.charAt(at);
		}
		if (at >= strlen) {	// no letters passes!
			return true;
		}
		int atplus = at + 1;
		if (Character.isUpperCase(string.charAt(at))
			&& (string.length() == atplus 
				|| Character.isLowerCase(string.charAt(atplus)))) {
			answer = true;
		}
		return answer;
  	}


		/**
		  * Return true if the string appears to contain an HTML
		  * document.  The real test is if the first non-whitespace
		  * in the string is an &lt;html&gt; tag.
		  * @since J-Bird 0.4.1
		  *
		*/

	public static final boolean isHTML(String string) {
		int lessat = string.indexOf('<');
		if (lessat < 0) {
			return false;
		}
		int greatat = string.indexOf('>', lessat);
		if (greatat < 0) {
			return false;
		}
		for (int idx = 0; idx < lessat; idx ++) {
			if (! Character.isWhitespace(string.charAt(idx))) {
				return false;
			}
		}
		String candidate = string.substring(lessat, greatat)
						.toLowerCase();
		if (candidate.startsWith("<html") 
			|| candidate.startsWith("<!doctype html")) {
			return true;
		}
		return false;
	}


		/**
		  * Position window on the screen. Attempt to place the
		  * attempt to position the dialog so that the first word
		  * of the notes appears right at the cursor so that the
		  * reader can begin reading immediately.  The operating 
		  * constraint is that the entire dialog must appear on the
		  * screen.
		  * <p>
		  * If the window is larger than the screen, the upper
		  * left-hand corner of the window will be placed in the
		  * upper left-hand corner of the screen.
		  * @since J-Bird 0.2.0
		*/
		
	public static final void placeWindow(Window window, Point point) {
			// try to place first word at location of cursor
			// so that a reader can start reading immediately
		int x = (int)point.getX() - 10;
		int y = (int)point.getY() - 10;
			// make sure that the window is on the screen
		Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
		Dimension diagsize = window.getSize();
		int rightx = x + diagsize.width;
		int bottomy = y + diagsize.height;
		if (rightx > screen.width) {
			x = x - (rightx - screen.width);
		}
		if (bottomy > screen.height) {
			y = y - (bottomy - screen.height);
		}
		if ( x < 0) {
			x = 0;
		} 
		if (y < 0) {
			y = 0;
		}
		window.setLocation(x, y);
	}

		/** 
		  * Return a string that of the given length and 
		  * the given character.
		  * @since J-Bird 0.3.0
		*/

	public static final String repeat(char achar, int length) {
		char[] guts = new char[length];
		for (int idx = 0; idx < length; idx ++) {
			guts[idx] = achar;
		}
		String answer = new String(guts);
		guts = null;
		return answer;
	}

  /**
    * Replace nulls in the two-dimensional array with the
    * given replacement.
    *
  */

  public static final void replaceNulls(Object[][] obaa,
                                        Object replacement) {
        int nrows = obaa.length;
        for (int rowidx = 0; rowidx < nrows; rowidx ++) {
                Object[] arow = obaa[rowidx];
                int ncols = arow.length;
                for (int colidx = 0; colidx < ncols; colidx ++) {
                        if (arow[colidx] == null) {
                                arow[colidx] = replacement;
                        } // end if
                } // end for colidx
        } // end for rowidx
  }


	/**
	  * Set the Calendar object to the last day of the
	  * field given.  For example, the argument is Calendar.MONTH, the
	  * calendar is set to the last day of the month, and if the 
	  * argument is Calendar.YEAR, the calendar is set to the last
	  * day of the year.
	  * <p>
	  * Hours, minutes, seconds, and milliseconds are zeroed out.
	  *
	*/

   public static final void setLastDay(Calendar calendar, int field) {
	switch (field) {
		case Calendar.YEAR:
			calendar.clear(Calendar.MONTH);
			calendar.set(Calendar.MONTH, 
				     calendar.getActualMaximum(Calendar.MONTH));
		case Calendar.MONTH:
			calendar.clear(Calendar.DAY_OF_MONTH);
			calendar.set(Calendar.DAY_OF_MONTH, 
				     calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
	}
	calendar.clear(Calendar.HOUR_OF_DAY);
	calendar.clear(Calendar.MINUTE);
	calendar.clear(Calendar.MILLISECOND);
   }

	/**
	  * Split a string into words.  Words are runs on
	  * non-whitespace separated by runs of whitespace.
	  * @since J-Bird 0.3.0
	  *
	*/

   public static final String[] split(String string) {
	int wc = countWords(string);
	String[] answer = new String[wc];
	if (wc == 0) {	// should work without this if! remove?
		return answer;
	}
	int maxat = string.length();
	int at = 0;
	for (int idx = 0; idx < wc; idx ++) {
				// advance to non whitespace
		while(Character.isWhitespace(string.charAt(at))) {
			at ++;
		}
		int beginat = at;
		at ++;
				// advance to whitespace or end of word
		while(at < maxat && ! Character.isWhitespace(string.charAt(at))) {
			at ++;
		}
		answer[idx] = string.substring(beginat, at);
	}
	return answer;
   }

	/** 
	  * Split scientific name into genus and species.
	  * @since J-Bird 0.4.1
	  *
	*/

   public static final String[] splitBinomial(String binomial,
					String[] answer) {
	binomial = binomial.trim();
	int sp = binomial.indexOf(' ');
	int tab = binomial.indexOf('\t');
	if (sp < 0 
		|| tab > -1 && tab < sp) {
		sp = tab;
	}
	if (sp < 0) {
		answer[0] = binomial;
		answer[1] = null;
		return answer;
	}
	answer[0] = binomial.substring(0, sp);
	char spchr = binomial.charAt(sp);
	while (spchr == ' ' || spchr == '\t') {
		sp ++;
		spchr = binomial.charAt(sp);
	}
	answer[1] = binomial.substring(sp);
	return answer;
   }


	/**
	  * Remove all html tags from the argument.
	  * @since J-Bird 0.2.0
	*/

   public static final String stripHTMLTags(String html) {
	int previous = html.length();
	int openat = html.lastIndexOf('<', previous);
	if (openat < 0) {		// nothing to do
		return html;
	}
	StringBuffer buf = new StringBuffer(html);
	while (openat > -1) {
		int closeat = html.indexOf('>', openat);
		if (closeat < 0) {	// tag opened but not closed
			closeat = previous;	// chop to last known good spot
		} else { 
			closeat ++;
		}
		buf.delete(openat, closeat);
		if (openat > 0) {
			previous = openat;
			openat = html.lastIndexOf("<", (openat - 1));
		} else {
			openat = -1;
		}
	}
	return buf.toString();
   }

	/**
	  * Take a passage of text in which paragraphs are separated by
	  * blank lines and return an html document that preserves the
	  * paragraph structure.  
	  * <p>
	  * Double new lines are replaced with paragraph boundaries.
	  * Then a preamble is inserted at the beginning and a trailer
	  * is placed at the end.
	  * @since J-Bird 0.1.3
	*/

   public static final String toHTML(String text) {
	text = trimLines(text);
	StringBuffer buf = new StringBuffer(text);
		// ignore all newlines at very end of document
	int at = text.length() - 1;
	while (at >= 0 && text.charAt(at) == '\n') {
		at --;
	}
		// look for blank line
	at = text.lastIndexOf("\n\n", at);
	while (at >= 0) {
			// check for multiple blank lines
		int first = at - 1;
		while (first >= 0 && text.charAt(first) == '\n') {
			first --;
		}
		first ++;
		at += 2;
			// replace blank lines with end par, new par
		buf.replace(first, at, "\n</p>\n<p>\n");
		first --;
		if (first > 0) {
			at = text.lastIndexOf("\n\n", first);
		} else {
			at = -1;
		}
	}
	buf.insert(0, "<html>\n<body>\n<p>\n");
	buf.append("\n</p>\n</body>\n</html>\n");
	return buf.toString();
   }

	/**
	  * Remove spaces and tabs that preceed newline 
	  * characters in a passage of text.
	  * @since J-Bird 0.1.3
	  *
	*/

   public static final String trimLines(String text) {
	StringBuffer buf = new StringBuffer(text);
	int at = text.lastIndexOf("\n");
	while (at >= 1) {
		int delat = at - 1;
		while (delat >= 0 && (text.charAt(delat) == ' '
					|| text.charAt(delat) == '\t')) {
			buf.deleteCharAt(delat);
			delat --;
		}	
		at --;
		at = text.lastIndexOf("\n", at);
	}
	return buf.toString();
   }

}
